import Module from "./Module"
import SymbolList from "../SymbolList"
import SweepData from "../data/SweepData"
import transitionTypeEnum from "../enum/transitionType.enum"
import CameraModule from "./CameraModule"
import l from "../math/63560"
import OpenDeferred from "../utils/OpenDeferred"
import u from "../math/79437"
import CameraData from "../data/CameraData"
import Logger from "../utils/Logger"
import DataSyncStore from "../storage/DataSyncStore"
import { SetCurrentSweepCommand, ClearCurrentSweepCommand } from "../command/sweep.command"
import Store from "../storage/Store"
import { CollectionSync } from "../storage/DataSync"
import T from "../math/19674"
import Sweep from "../object/Sweep"
const A = new Logger("sweeps")

export interface sweepDataItem {
  alignment_type: string
  floor_index: number
  floor_position: postion
  index: number
  model_id: string
  neighbors: number[]
  placement: string
  position: postion
  room_index: number
  rotation: postion
  sweep_name: null
  sweep_uuid: string
  cid: string
  tags: string[]
}

interface postion {
  x: number
  y: number
  z: number
  w?: number
}

function deserialize(forceThrow = !0) {
  function action(item: sweepDataItem, sourcekey: keyof sweepDataItem, sweep: Sweep, targetKey: string, func?: (e: any) => any) {
    const val = item[sourcekey]
    if (void 0 !== val) sweep[targetKey] = func ? func(val) : val
    else if (forceThrow) throw Error(`Required property missing in data: ${sourcekey}`)
  }
  return (data: sweepDataItem[]) => {
    if (!data || Object.keys(data).length < 1) return null
    const result: sweepDataRes = {}
    try {
      const n: Sweep[] = []
      for (const item of data) {
        const sweep = new Sweep()
        action(item, "index", sweep, "index")
        if (n[sweep.index]) throw Error("Duplicate sweep index found")
        n[sweep.index] = sweep
        action(item, "sweep_uuid", n[sweep.index], "id", e => e.replace(/-/g, ""))
      }
      for (const item of data) {
        const sweep = n[item.index]
        action(item, "neighbors", sweep, "neighbours", e => e.map(e => (n[e] ? n[e].id : void 0)).filter(e => void 0 !== e))
        action(item, "alignment_type", sweep, "alignmentType", e =>
          "string" == typeof e && sweepAlignmentEnum.hasOwnProperty(e.toUpperCase()) ? e : sweepAlignmentEnum.UNALIGNED
        )
        action(item, "placement", sweep, "placementType", () => {
          if (sweep.alignmentType === sweepAlignmentEnum.ALIGNED) return sweepPlacementEnum.AUTO
          return "string" == typeof item.placement && sweepPlacementEnum.hasOwnProperty(item.placement.toUpperCase())
            ? item.placement
            : sweepPlacementEnum.UNPLACED
        })
        action(item, "rotation", sweep, "rotation", e => T.ep.fromVisionQuaternion(e))
        action(item, "position", sweep, "position", e => T.ep.fromVisionVector(e))
        action(item, "floor_position", sweep, "floorPosition", e => T.ep.fromVisionVector(e))
        action(item, "sweep_name", sweep, "name")
        action(item, "floor_index", sweep, "floorId", e => ("number" == typeof e && e > -1 ? String(e) : null))
        action(item, "room_index", sweep, "roomId", e => ("number" == typeof e && e > -1 ? String(e) : null))
        action(item, "tags", sweep, "enabled", e => -1 !== e.indexOf("showcase"))
        action(item, "tags", sweep, "vrenabled", e => -1 !== e.indexOf("vr"))
        sweep.uuid = sweep.id
        result[sweep.id] = sweep
      }
    } catch (e) {
      A.error("Error reading sweeps list", e)
    }
    return 0 === Object.keys(result).length ? null : result
  }
}

import aggregationTypeEnum from "../enum/aggregationType.enum"
import { SaveCommand } from "../command/data.command"

import { SweepTransitionError, InvalidSweepError } from "../exception/sweep.exception"
import sweepAlignmentEnum from "../enum/sweepAlignment.enum"
import sweepPlacementEnum from "../enum/sweepPlacement.enum"
import { BeginMoveToSweepMessage, EndMoveToSweepMessage, MoveToSweepMessage } from "../message/sweeps.messge"
import storageTypeEnum from "../enum/storageType.enum"
import dataTypeEnum from "../enum/dataType.enum"
import MapMonitor from "../collection/MapMonitor"
import EngineContext from "../EngineContext"
import SweepPanoTilingModule from "./SweepPanoTilingModule"
import SweepPanoSolidModule from "./SweepPanoSolidModule"
import MarketContext from "../MarketContext"

export interface sweepDataRes {
  [key: string]: Sweep
}

const X = new Logger("sweep-module")
export default class SweepDataModule extends Module {
  internalProgress: boolean
  defaultEasing: any
  moveToSweep: any
  sweepData: SweepData
  cameraData: CameraData
  sweepPanoModule: SweepPanoTilingModule | SweepPanoSolidModule
  engine: EngineContext
  instantSweepTransition: any
  cameraModule: CameraModule
  tryInferFloorIds: any
  market: MarketContext
  store: DataSyncStore<any>
  monitor: MapMonitor<any>
  activateSweep: any
  beginSweepTransition: any
  defaultSweepCameraMove: any
  endSweepTransition: any
  activateSweepUnsafe: any

  constructor() {
    super(...arguments)
    this.name = "sweep-data"
    this.internalProgress = !1
    this.defaultEasing = u.qb
    this.moveToSweep = e => {
      const t = new OpenDeferred()
      e.prevSweepId = e.prevSweepId || this.sweepData.currentSweep
      this.activateSweep(e)
        .then(() => (this.beginSweepTransition(e), this.defaultSweepCameraMove(t, e)))
        .then(() => {
          this.endSweepTransition(e)
          t.resolve()
        })
        .catch(i => {
          X.debug(`Failure moving to sweep ${e.sweepId}`, i), t.reject(i)
        })
      return t.promise()
    }
    this.activateSweep = async e => {
      if (!this.sweepData.canTransition() || !this.cameraData.canTransition())
        throw new SweepTransitionError("Tried to start transition while another transition was active")
      if (!this.sweepData.containsSweep(e.sweepId)) throw new InvalidSweepError(`Invalid sweep ${e.sweepId}`)
      return this.activateSweepUnsafe(e)
    }
    this.activateSweepUnsafe = async e => {
      this.sweepPanoModule || (this.sweepPanoModule = await this.engine.getModuleBySymbol(SymbolList.SWEEP_PANO))
      return this.sweepPanoModule.getRenderer().activateSweep(e.sweepId)
    }
    this.instantSweepTransition = e => {
      const t = this.sweepData.currentSweep
      this.beginSweepTransition({
        sweepId: e,
        transitionTime: 0,
        internalProgress: !0,
        prevSweepId: t
      }),
        (this.sweepData.currentSweep = e),
        this.sweepData.commit(),
        this.engine.broadcast(new MoveToSweepMessage(e, t)),
        this.endSweepTransition({ sweepId: e, prevSweepId: t })
    }
    this.beginSweepTransition = e => {
      this.sweepData.activateTransition(e.sweepId, e.prevSweepId || this.sweepData.currentSweep),
        (this.internalProgress = e.internalProgress || !1),
        this.sweepData.transition.progress.modifyAnimation(0, 1, e.transitionTime || 0, e.easing || this.defaultEasing),
        this.sweepData.commit(),
        this.engine.broadcast(new BeginMoveToSweepMessage(e.sweepId, e.prevSweepId))
    }
    this.endSweepTransition = e => {
      const t = this.sweepData.getSweep(e.sweepId)
      this.sweepData.deactivateTransition(),
        (this.sweepData.currentSweep = e.sweepId),
        this.sweepData.transition.progress.stop(1),
        (this.internalProgress = !1),
        this.sweepData.commit(),
        this.engine.broadcast(new EndMoveToSweepMessage(e.sweepId, t.alignmentType, t.placementType, e.prevSweepId))
    }
    this.defaultSweepCameraMove = async (e, t) => {
      const i = {
          position: this.sweepData.getSweep(t.sweepId).position,
          rotation: t.rotation
        },
        n = l.AN(this.cameraData.aspect())
      let s = transitionTypeEnum.Interpolate
      void 0 !== t.transitionType && transitionTypeEnum[t.transitionType] && (s = t.transitionType)
      const r = this.cameraModule.moveTo({
        transitionType: s,
        pose: i,
        transitionTime: t.transitionTime,
        transitionSpeedMultiplier: t.transitionSpeedMultiplier,
        projection: n,
        easing: t.easing,
        rotationDelay: t.rotationDelay
      })
      let a = !1
      return (
        r.progress(i => {
          this.sweepData.transition.progress.updateProgress(i),
            e.notify(i),
            i >= 0.5 &&
              !a &&
              ((a = !0),
              (this.sweepData.currentSweep = t.sweepId),
              this.sweepData.commit(),
              this.engine.broadcast(new MoveToSweepMessage(t.sweepId, t.prevSweepId)))
        }),
        r.nativePromise()
      )
    }
    this.tryInferFloorIds = (e: sweepDataRes) => {
      for (const t of Object.values(e))
        if (t.isAligned() && null === t.floorId) {
          X.warn(`Aligned sweep ${t.index} has no floor association`)
          const i = t.neighbours.map(i => ({
            id: i,
            floorId: e[i].floorId,
            distance: e[i].floorPosition.distanceToSquared(t.floorPosition)
          }))
          i.sort((e, t) => e.distance - t.distance)
          const n = i.find(e => e.floorId)
          n && (X.debug(`Inferred Sweep ${t.index} floor as ${n.floorId} from neighbors`), (t.floorId = n.floorId))
        }
    }
  }

  async init(e, t: EngineContext) {
    const { readonly: i } = e
    this.engine = t
    this.market = this.engine.market
    this.store = this.getStore(e)
    const n: sweepDataRes = (await this.store.read()) || {}
    ;[(this.filterSweepData, this.tryInferFloorIds)].forEach(e => e(n))
    this.sweepData = new SweepData(n)
    this.market.register(this, SweepData, this.sweepData)
    if (!i) {
      this.monitor = new MapMonitor(this.sweepData.getCollection(), { aggregationType: aggregationTypeEnum.NextFrame, shallow: !0 }, t)
      this.monitor.onChanged(() => this.engine.commandBinder.issueCommand(new SaveCommand({ dataTypes: [dataTypeEnum.SWEEPS] }))),
        this.store instanceof DataSyncStore && this.store.addMonitor(this.monitor)
    }
    this.cameraModule = await t.getModule(CameraModule)
    this.cameraData = await t.market.waitForData(CameraData)
    this.bindings.push(
      t.commandBinder.addBinding(ClearCurrentSweepCommand, async () => {
        this.sweepData.currentSweep = void 0
        this.sweepData.commit()
      }),
      t.commandBinder.addBinding(SetCurrentSweepCommand, async e => {
        if (e.sweepId)
          try {
            const t = e.sweepId
            await this.activateSweepUnsafe({ sweepId: t })
            const i = this.sweepData.currentSweep
            this.beginSweepTransition({
              sweepId: t,
              transitionTime: 0,
              internalProgress: !0,
              prevSweepId: i
            })
            this.sweepData.currentSweep = t
            this.endSweepTransition({ sweepId: t, prevSweepId: i })
            this.sweepData.commit()
          } catch (e) {
            X.error(e)
          }
        else (this.sweepData.currentSweep = void 0), this.sweepData.commit()
      })
    )
  }
  getStore(e) {
    const { modelId, baseUrl, queue } = e
    X.info(`Loading Sweeps from ${storageTypeEnum.JSONSTORE}`)
    let path = `${baseUrl}/scene-portal/${modelId}/models/sweeps?tag=showcase`
    const ReadOnlyStore = new Store.ReadOnlyStore({
      queue,
      path,
      deserialize: deserialize(!0),
      cachedData: { data: null }
    })
    const Sync = new CollectionSync({ publishStores: [ReadOnlyStore] })
    return new DataSyncStore(Sync, {
      dataType: dataTypeEnum.SWEEPS,
      engine: this.engine
    })
  }
  filterSweepData(e: sweepDataRes) {
    for (const t in e) {
      const i = e[t]
      i.neighbours = i.neighbours.filter(t => t in e)
    }
  }
  onUpdate(e) {
    this.internalProgress && this.sweepData.transition.active && this.sweepData.transition.progress.tick(e)
  }
}
